package com.javacoo.junit.generator.maven;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.model.Resource;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;

import com.javacoo.junit.generator.api.plugin.GeneratorPlugin;
import com.javacoo.junit.generator.env.ContextEnvment;
import com.javacoo.junit.generator.exception.InvalidConfigurationException;
import com.javacoo.junit.generator.exception.XMLParserException;
import com.javacoo.junit.generator.internal.ObjectFactory;
import com.javacoo.junit.generator.internal.util.ClassloaderUtility;
import com.javacoo.junit.generator.internal.util.messages.Messages;
import com.javacoo.junit.generator.logging.LogFactory;
import com.javacoo.junit.generator.maven.callback.MavenProgressCallback;
import com.javacoo.junit.generator.maven.log.MavenLogFactory;
import com.javacoo.xkernel.spi.ExtensionLoader;

/**
 * JUnit单元测试生成工具 maven插件
 * <li></li>
 *
 * @author: duanyong@jccfc.com
 * @since: 2020/12/31 14:26
 */
@Mojo(name = "generate", defaultPhase = LifecyclePhase.GENERATE_SOURCES,
    requiresDependencyResolution = ResolutionScope.TEST)
public class JunitGeneratorMojo extends AbstractMojo {
    /** ClassLoader ThreadLocal */
    private ThreadLocal<ClassLoader> savedClassloader = new ThreadLocal<>();

    /**
     * 当前Maven工程
     */
    @Parameter(property = "project", required = true, readonly = true)
    private MavenProject project;

    /**
     * 项目根目录
     */
    @Parameter(property = "generate.baseDirectory",
        defaultValue = "${project.basedir}", required = true)
    private File baseDirectory;

    /**
     * 配置文件.
     */
    @Parameter(property = "generate.configurationFile",
        defaultValue = "${project.basedir}/src/main/resources/junitGeneratorConfig.xml", required = true)
    private File configurationFile;

    /**
     * 指定mojo是否将进度消息写入日志。
     */
    @Parameter(property = "generate.verbose", defaultValue = "true")
    private boolean verbose;

    /**
     * 指定mojo是否覆盖现有Java文件。默认值为false。
     * <br>
     * 请注意:XML文件总是合并的
     */
    @Parameter(property = "generate.overwrite", defaultValue = "false")
    private boolean overwrite;
    /**
     * 生成代码前，是否备份已有文件
     * <br>
     * 请注意:overwrite 为 false 时生效
     */
    @Parameter(property = "generate.backup", defaultValue = "false")
    private boolean backup;

    /**
     * 要生成的上下文的逗号分隔列表
     */
    @Parameter(property = "generate.contexts")
    private String contexts;

    /**
     * 是否跳过生成
     */
    @Parameter(property = "generate.skip", defaultValue = "false")
    private boolean skip;

    /**
     * 生成器插件
     */
    @Parameter(property = "generate.plugin", defaultValue = "default")
    private String plugin;

    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        if (skip) {
            getLog().info("Junit Test generator is skipped.");
            return;
        }
        //保存ClassLoader
        saveClassLoader();
        //设置日志工厂
        LogFactory.setLogFactory(new MavenLogFactory(this));
        //计算类路径
        calculateClassPath();
        //将资源目录添加到类路径
        loadResourceDirectoryToClassPath();
        //一些必要的校验
        verify();
        //定义警告信息集合
        List<String> warnings = new ArrayList<>();
        try {
            //构建执行上下文环境对象
            ContextEnvment contextEnvment = ContextEnvment.builder()
                .backup(backup)
                .baseDirectory(baseDirectory)
                .callback(new MavenProgressCallback(getLog(), verbose))
                .classloader(savedClassloader)
                .configurationFile(configurationFile)
                .contexts(contexts)
                .extraProperties(project.getProperties())
                .overwrite(overwrite)
                .warnings(warnings)
                .build();
            //查找执行器插件
            GeneratorPlugin generatorPlugin = ExtensionLoader.getExtensionLoader(GeneratorPlugin.class).getExtension(plugin).init(contextEnvment);
            //执行生成
            generatorPlugin.generate();
        } catch (XMLParserException e) {
            for (String error : e.getErrors()) {
                getLog().error(error);
            }
            throw new MojoExecutionException(e.getMessage());
        } catch (IOException e) {
            e.printStackTrace();
            throw new MojoExecutionException(e.getMessage());
        } catch (InvalidConfigurationException e) {
            for (String error : e.getErrors()) {
                getLog().error(error);
            }
            throw new MojoExecutionException(e.getMessage());
        }

        for (String error : warnings) {
            getLog().warn(error);
        }
        //恢复ClassLoader
        restoreClassLoader();
    }

    /**
     * 参数校验
     * <p>说明:</p>
     * <li>configurationFile非空校验</li>
     * @author duanyong@jccfc.com
     * @date 2021/1/11 22:34
     */
    private void verify() throws MojoExecutionException {
        if (configurationFile == null) {
            throw new MojoExecutionException(
                Messages.getString("RuntimeError.0"));
        }

        if (!configurationFile.exists()) {
            throw new MojoExecutionException(Messages.getString(
                "RuntimeError.1", configurationFile.toString()));
        }
    }

    /**
     * 计算类路径
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/5 10:57
     * @return: void
     */
    private void calculateClassPath() throws MojoExecutionException {
        try {
            // 将项目编译类路径添加到插件类路径中，以便可以找到项目依赖类
            Set<String> entries = new HashSet<>();
            entries.addAll(project.getCompileClasspathElements());
            entries.addAll(project.getTestClasspathElements());

            ClassLoader contextClassLoader = ClassloaderUtility.getCustomClassloader(entries);
            Thread.currentThread().setContextClassLoader(contextClassLoader);
        } catch (DependencyResolutionRequiredException e) {
            throw new MojoExecutionException("需要依赖项解析", e);
        }
    }
    /**
     * 将资源目录添加到类路径
     * <li>这是支持在生成中使用属性文件。通常，属性文件在项目的源代码树中，但插件类路径没有包括项目类路径。</li>
     * @author duanyong@jccfc.com
     * @date 2020/12/31 15:58
     * @return: void
     */
    private void loadResourceDirectoryToClassPath() {
        List<Resource> resources = project.getResources();
        List<String> resourceDirectories = new ArrayList<>();
        for (Resource resource: resources) {
            resourceDirectories.add(resource.getDirectory());
        }
        ClassLoader cl = ClassloaderUtility.getCustomClassloader(resourceDirectories);
        ObjectFactory.addExternalClassLoader(cl);
    }

    /**
     * 保存ClassLoader
     * <li>保存当前线程ContextClassLoader</li>
     * @author duanyong@jccfc.com
     * @date 2020/12/31 14:42
     * @return: void
     */
    private void saveClassLoader() {
        savedClassloader.set(Thread.currentThread().getContextClassLoader());
    }
    /**
     * 恢复ClassLoader
     * <li>设置当前线程ContextClassLoader 为 保存的ContextClassLoader</li>
     * @author duanyong@jccfc.com
     * @date 2020/12/31 14:44
     * @return: void
     */
    private void restoreClassLoader() {
        Thread.currentThread().setContextClassLoader(savedClassloader.get());
    }
}
